#include <Engine/StdAfx.h>
#include <Engine/RenderObjects/MeshObject.h>
#include <Engine/IRenderer.h>
#include <Engine/GlobalEnv.h>
#include <Engine/ICamera.h>
#include <Engine/IConsole.h>

namespace fastbird
{
	//----------------------------------------------------------------------------
	IMeshObject* IMeshObject::CreateMeshObject()
	{
		return new MeshObject;
	}

	//----------------------------------------------------------------------------
	MeshObject::MeshObject()
	{
		mTopology = PRIMITIVE_TOPOLOGY_TRIANGLELIST;
		if (gFBEnv && gFBEnv->pRenderer && gFBEnv->pRenderer->GetCamera())
			mObjectConstants.gWorldViewProj = gFBEnv->pRenderer->GetCamera()->GetViewProjMat();
		else
			mObjectConstants.gWorldViewProj.MakeIdentity();

		mObjectConstants.gWorld.MakeIdentity();
		SetDepthStencilState(DEPTH_STENCIL_DESC());
	}

	//----------------------------------------------------------------------------
	MeshObject::~MeshObject()
	{
	}

	//----------------------------------------------------------------------------
	void MeshObject::PreRender()
	{
		if (mObjFlag & IObject::OF_HIDE)
			return;
		mObjectConstants.gWorld.SetTranslation( mTransformation.GetTranslation() );
		mObjectConstants.gWorldViewProj = gFBEnv->pRenderer->GetCamera()->GetViewProjMat() * mObjectConstants.gWorld;
	}

	//----------------------------------------------------------------------------
	void MeshObject::Render()
	{
		if (mObjFlag & IObject::OF_HIDE)
			return;

		if (!mMaterial)
		{
			assert(0);
			return;
		}

		if (!gFBEnv->pConsole->GetEngineCommand()->r_noObjectConstants)
			gFBEnv->pRenderer->UpdateObjectConstantsBuffer(&mObjectConstants);

		bool includeInputLayout = true;
		if (mInputLayoutOverride)
		{
			mInputLayoutOverride->Bind();
			includeInputLayout = false;
		}
		mMaterial->Bind(includeInputLayout);

		gFBEnv->pRenderer->SetPrimitiveTopology(mTopology);
		BindRenderStates();
		
		const unsigned int numBuffers = 3;
		IVertexBuffer* buffers[numBuffers] = {mVertexBuffer, mVBNormal, mVBUV};
		unsigned int strides[numBuffers] = {mVertexBuffer->GetStride(), 
			mVBNormal ? mVBNormal->GetStride() : 0,
			mVBUV ? mVBUV->GetStride() :0 };
		unsigned int offsets[numBuffers] = {0, 0, 0};
		gFBEnv->pRenderer->SetVertexBuffer(0, numBuffers, buffers, strides, offsets);
		if (mIndexBuffer)
		{
			gFBEnv->pRenderer->SetIndexBuffer(mIndexBuffer);
			gFBEnv->pRenderer->DrawIndexed(mIndexBuffer->GetNumIndices(), 0, 0);
		}
		else
		{
			gFBEnv->pRenderer->Draw(mVertexBuffer->GetNumVertices(), 0);
			
		}
	}

	//----------------------------------------------------------------------------
	void MeshObject::PostRender()
	{
		if (mObjFlag & IObject::OF_HIDE)
			return;
	}

	//----------------------------------------------------------------------------
	IObject* MeshObject::Clone() const
	{
		MeshObject* cloned = (MeshObject*)IMeshObject::CreateMeshObject();
		cloned->mVertexBuffer = mVertexBuffer;
		cloned->mIndexBuffer = mIndexBuffer;
		cloned->mMaterial = mMaterial;
		cloned->mInputLayoutOverride = mInputLayoutOverride;
		cloned->mBoundingVolume = mBoundingVolume;
		cloned->mBoundingVolumeWorld->SetCenter(mBoundingVolumeWorld->GetCenter());
		cloned->mBoundingVolumeWorld->SetRadius(mBoundingVolumeWorld->GetRadius());
		cloned->mRasterizerState = mRasterizerState;
		cloned->mBlendState = mBlendState;
		cloned->mDepthStencilState = mDepthStencilState;
		cloned->mGameType = mGameType;
		cloned->mDestructing = mDestructing;
		cloned->mTransformation = mTransformation;
		cloned->mTransformation.SetTranslate(mTransformation.GetTranslation()+Vec3(1.f, 0, 0));
		cloned->mDistToCam = mDistToCam;
		cloned->mTopology = mTopology;
		cloned->mObjectConstants = mObjectConstants;
		cloned->mVBNormal = mVBNormal;
		cloned->mVBUV = mVBUV;
		return cloned;
	}

	//----------------------------------------------------------------------------
	bool MeshObject::LoadOgreMesh(const char* filename)
	{
		return true;
	}

	//----------------------------------------------------------------------------
	void MeshObject::ClearMeshData()
	{
		mPositions.clear();
		mNormals.clear();
		mUVs.clear();

		mPositions.resize(0);
		mNormals.resize(0);
		mUVs.reserve(0);
	}

	//----------------------------------------------------------------------------
	void MeshObject::StartModification()
	{
		
	}

	//----------------------------------------------------------------------------
	void MeshObject::AddTriangle(const Vec3& pos0, const Vec3& pos1, const Vec3& pos2)
	{
		mPositions.push_back(pos0);
		mPositions.push_back(pos1);
		mPositions.push_back(pos2);
	}

	//----------------------------------------------------------------------------
	void MeshObject::AddQuad(const Vec3 pos[4], const Vec3 normal[4])
	{
		mPositions.push_back(pos[0]);
		mPositions.push_back(pos[1]);
		mPositions.push_back(pos[2]);

		mPositions.push_back(pos[2]);
		mPositions.push_back(pos[1]);
		mPositions.push_back(pos[3]);

		
		mNormals.push_back(normal[0]);
		mNormals.push_back(normal[1]);
		mNormals.push_back(normal[2]);

		mNormals.push_back(normal[2]);
		mNormals.push_back(normal[1]);
		mNormals.push_back(normal[3]);
	}

	void MeshObject::AddQuad(const Vec3 pos[4], const Vec3 normal[4], const Vec2 uv[4])
	{
		AddQuad(pos, normal);
		mUVs.push_back(uv[0]);
		mUVs.push_back(uv[1]);
		mUVs.push_back(uv[2]);

		mUVs.push_back(uv[2]);
		mUVs.push_back(uv[1]);
		mUVs.push_back(uv[3]);
	}

	//----------------------------------------------------------------------------
	void MeshObject::SetPositions(const Vec3* p, size_t numVertices)
	{
		mPositions.assign(p, p+numVertices);
	}

	
	void MeshObject::SetNormals(const Vec3* n, size_t numNormals)
	{
		mNormals.assign(n, n+numNormals);
	}

	void MeshObject::SetUVs(const Vec2* uvs, size_t numUVs)
	{
		mUVs.assign(uvs, uvs+numUVs);
	}

	//----------------------------------------------------------------------------
	void MeshObject::SetIndices(const UINT* indices, size_t numIndices)
	{
		if (!gFBEnv)
			return;
		if (numIndices <= std::numeric_limits<USHORT>::max())
		{
			std::vector<USHORT> sIndices(indices, indices+numIndices);
			mIndexBuffer = 
				gFBEnv->pRenderer->CreateIndexBuffer(&sIndices[0], numIndices, INDEXBUFFER_FORMAT_16BIT);
		}
		else
		{
			mIndexBuffer = 
				gFBEnv->pRenderer->CreateIndexBuffer((void*)indices, numIndices, INDEXBUFFER_FORMAT_32BIT);
		}
	}

	//----------------------------------------------------------------------------
	void MeshObject::SetIndices(const USHORT* indices, size_t numIndices)
	{
		mIndexBuffer = gFBEnv->pRenderer->CreateIndexBuffer((void*)indices, numIndices, INDEXBUFFER_FORMAT_16BIT);
	}

	//----------------------------------------------------------------------------
	Vec3* MeshObject::GetVertices(size_t& outNumVertices)
	{
		outNumVertices = mPositions.size();
		outNumVertices > 0 ? &mPositions[0] : 0;
		if (outNumVertices)
			return &mPositions[0];
		else
			return 0;
	}

	//----------------------------------------------------------------------------
	void MeshObject::EndModification(bool keepMeshData)
	{
		if (!mPositions.empty() && gFBEnv && gFBEnv->pRenderer)
		{
			mVertexBuffer = gFBEnv->pRenderer->CreateVertexBuffer(
				&mPositions[0], sizeof(Vec3), mPositions.size(), BUFFER_USAGE_IMMUTABLE, BUFFER_CPU_ACCESS_NONE);
		}
		else
		{
			mVertexBuffer = 0;
		}
		if (!mNormals.empty() && gFBEnv && gFBEnv->pRenderer)
		{
			assert(mPositions.size() == mNormals.size());
			mVBNormal = gFBEnv->pRenderer->CreateVertexBuffer(
				&mNormals[0], sizeof(Vec3), mNormals.size(), BUFFER_USAGE_IMMUTABLE, BUFFER_CPU_ACCESS_NONE);
		}
		else
		{
			mVBNormal = 0;
		}
		if (!mUVs.empty() && gFBEnv && gFBEnv->pRenderer)
		{
			assert(mPositions.size() == mUVs.size());
			mVBUV = gFBEnv->pRenderer->CreateVertexBuffer(
				&mUVs[0], sizeof(Vec2), mUVs.size(), BUFFER_USAGE_IMMUTABLE, BUFFER_CPU_ACCESS_NONE);
		}
		else
		{
			mVBUV = 0;
		}

		mBoundingVolume->ComputeFromData(&mPositions[0], mPositions.size());
		mBoundingVolumeWorld->SetCenter(mBoundingVolume->GetCenter());
		mBoundingVolumeWorld->SetRadius(mBoundingVolume->GetRadius());

		if (!keepMeshData)
			ClearMeshData();
	}

	//----------------------------------------------------------------------------
	void MeshObject::SetTopology(PRIMITIVE_TOPOLOGY topology)
	{
		mTopology = topology;
	}

	//----------------------------------------------------------------------------
	PRIMITIVE_TOPOLOGY MeshObject::GetTopology()
	{
		return mTopology;
	}

	//----------------------------------------------------------------------------
	void MeshObject::SetMaterial(const char* name)
	{
		// Init Material
		mMaterial = fastbird::IMaterial::CreateMaterial(name);
		assert(mMaterial);
	}
}